package com.ak.service;

import cn.hutool.core.collection.CollectionUtil;
import com.ak.config.GatewayConfigProperties;
import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.concurrent.Executor;

/**
 * 通过Nacos下发的动态配置，监听nacos中路由配置
 *
 * @author xk
 * @create 2023/12/6 14:17
 * @since 1.0.0
 */
@Slf4j
@Component
@DependsOn({"gatewayConfiguration"})
public class DynamicRouteServiceImplNacos implements ApplicationEventPublisherAware {

    @Autowired
    private GatewayConfigProperties gatewayConfigProperties;
    @Autowired
    private ConfigService configService;

//    private final DynamicRouteServiceImpl dynamicRouteService;
//
//    public DynamicRouteServiceImplNacos(DynamicRouteServiceImpl dynamicRouteService) {
//        this.dynamicRouteService = dynamicRouteService;
//    }
    /**
     * 写路由定义
     */
    private final RouteDefinitionWriter routeDefinitionWriter;
    /**
     * 获取路由定义
     */
    private final RouteDefinitionLocator routeDefinitionLocator;
    /**
     * 事件发布
     */
    private ApplicationEventPublisher publisher;

    public DynamicRouteServiceImplNacos(RouteDefinitionWriter routeDefinitionWriter,
                                   RouteDefinitionLocator routeDefinitionLocator) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeDefinitionLocator = routeDefinitionLocator;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        // 完成事件推送句柄的初始化
        this.publisher = applicationEventPublisher;
    }

    /**
     * bean在容器中构造完成之后会立即执行当前的init方法
     * 加载路由信息，注册监听器
     */
    @PostConstruct
    public void init(){
        log.info("网关路由初始化...");
        try {
            //初始化nacos配置客户端
            if(configService==null){
                log.error("初始化配置服务异常,配置服务是null!");
                return;
            }
            //通过 nacos config 并指定路由配置路径去获取路由配置
            String configInfo = configService.getConfig(gatewayConfigProperties.getData_id(),
                    gatewayConfigProperties.getNacos_route_group(),
                    gatewayConfigProperties.DEFAULT_TIMEOUT);
            log.info("当前网关配置信息:[{}]:",configInfo);
            //反序列化
            List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
            if(CollectionUtil.isNotEmpty(routeDefinitions)){
                //新增
                for (RouteDefinition routeDefinition : routeDefinitions) {
                    log.info("初始化 路由定义对象 信息:[{}]:",routeDefinition);
                    this.addRouteDefinition(routeDefinition);
                }
            }
        } catch (Exception e) {
            log.error("网关路由初始化失败:[{}]",e.getMessage());
        }
        //设置监听器
        dynamicRouteServiceImplNacosByListener(gatewayConfigProperties.getNacos_route_data_id(),gatewayConfigProperties.getNacos_route_group());
    }

    /**
     * 监听 Nacos下发的动态路由配置
     *
     * @param dataId
     * @param group
     */
    private void dynamicRouteServiceImplNacosByListener(String dataId, String group) {
        try {
            //给Nacos config 客户端增加一个监听器
            configService.addListener(dataId, group, new Listener() {
                /**
                 * 自己提供线程池执行操作
                 * @return
                 */
                @Override
                public Executor getExecutor() {
                    return null;
                }

                /**
                 * 监听器收到接收到配置变更信息
                 * @param configInfo nacos 中最新配置信息
                 */
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("接收的配置信息:[{}]", configInfo);
                    List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
                    updateList(routeDefinitions);
                    log.info("更新后的路由配置信息:[{}]", routeDefinitions.toString());
                }
            });
        } catch (Exception e) {
            log.error("监听 Nacos下发的动态路由配置异常:[{}]", e.getMessage());
        }
    }
    /**
     * 从nacos读取路由配hi，写道gateway中
     * <h2>增加路由定义</h2>
     */
    public String addRouteDefinition(RouteDefinition definition) {

        log.info("gateway add route: [{}]", definition);

        // 保存路由配置并发布
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        // 发布事件通知给 Gateway, 同步新增的路由定义
        //TODO 发布事件这里没有往下运行发生死锁，是低版本gateway没有获取到路由的问题，需要定义一个RouteDefinitionRepository实现
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }

    /**
     * <h2>更新路由</h2>
     */
    public String updateList(List<RouteDefinition> definitions) {

        log.info("更新网关路由: [{}]", definitions);

        // 先拿到当前 Gateway 中存储的路由定义
        List<RouteDefinition> routeDefinitionsExits =
                routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
        if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
            // 清除掉之前所有的 "旧的" 路由定义
            routeDefinitionsExits.forEach(rd -> {
                deleteById(rd.getId());
                log.info("清除掉之前所有的 旧的 路由定义: [{}]", rd);
            });
        }

        // 把更新的路由定义同步到 gateway 中
        definitions.forEach(definition -> updateByRouteDefinition(definition));
        return "success";
    }

    /**
     * <h2>根据路由 id 删除路由配置</h2>
     */
    private String deleteById(String id) {

        try {
            log.info("要删除的路由id: [{}]", id);
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            // 发布事件通知给 gateway 更新路由定义
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "删除成功";
        } catch (Exception ex) {
            log.error("删除网关路由失败: [{}]", ex.getMessage(), ex);
            return "删除失败";
        }
    }

    /**
     * <h2>更新路由</h2>
     * 更新的实现策略比较简单: 删除 + 新增 = 更新
     */
    private String updateByRouteDefinition(RouteDefinition definition) {

        try {
            log.info("更新网关路由: [{}]", definition);
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception ex) {
            return "更新失败,没有查到更新的网关路由id: " + definition.getId();
        }

        try {
            this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "成功";
        } catch (Exception ex) {
            return "更新路由失败！";
        }
    }



}
